/*-------------------------------------------------------------------------
 *
 * win32_sema.c
 *	  Microsoft Windows Win32 信号量仿真
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/backend/port/win32_sema.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"

static HANDLE *mySemSet;		/* 目前已获取的信号量集的 ID */
/* 目前已获取的信号量集的数量 */
/* mySemaSet 数组的分配大小 */
/*
 * 报告所需的共享内存量用于信号量
 */
/* 在 Windows 上不需要共享内存 */
/*
 * PGReserveSemaphores --- 初始化信号量支持
 *
 * 在 Win32 实现中，我们按需获取信号量；maxSemas 参数仅用于确定用于跟踪
 * 后续释放的已获取信号量的数组大小。我们使用匿名信号量，
 * 因此信号量在最后一个引用进程退出时会自动释放。
 */
/*
 * 在关闭或共享内存重新初始化时释放信号量
 *
 * （作为 on_shmem_exit 回调调用，因此参数列表比较奇怪）
 */
/*
 * PGSemaphoreCreate
 *
 * 分配一个初始计数为 1 的 PGSemaphore 结构
 */
/* 无法在后端做到这一点，因为静态状态属于主进程 */ 
/* 我们不需要命名信号量 */ 
/* 成功完成 */ 
/*
 * PGSemaphoreReset
 *
 * 将先前初始化的 PGSemaphore 重置为计数 0
 */
/*
	 * 在 Win32 中没有直接的 API，所以我们必须通过重复的 trylock
	 * 将信号量的计数降到 0。
	 */
  
/* 循环 */ 
/*
 * PGSemaphoreLock
 *
 * 锁定一个信号量（减少计数），如果计数 < 0 则阻塞。
 */
/*
	 * 注意：pgwin32_signal_event 应该首先执行，以确保在多个事件被设置时
	 * 会被报告。我们希望确保待处理的信号得到处理。
	 */
  
/*
	 * 像 PGSemaphoreLock 的其他实现一样，我们需要每次循环时检查取消/退出中断。
	 * 但在这里，没有关于系统调用是否会内部处理信号的隐藏魔法——我们自己处理。
	 */
  
/* 信号事件已被设置 - 我们有一个信号要发送 */ 
/* 我们得到了它！ */ 
/*
				 * 系统中断了等待，以执行此线程中的 I/O
				 * 完成例程或异步过程调用。PostgreSQL 并不引发这两种情况，
				 * 但不典型的加载的 DLL 或甚至其他进程可能会这样做。
				 * 现在，继续等待。
				 */
  
/*
 * PGSemaphoreUnlock
 *
 * 解锁一个信号量（增加计数）
 */
/*
 * PGSemaphoreTryLock
 *
 * 仅在能够不阻塞的情况下锁定信号量
 */
/* 我们得到了它！ */ 
/* 无法获取 */ 
/* 否则我们会有麻烦 */ 
/* 保持编译器安静 */
static int	numSems;			/* 到目前为止获得的信号量集合的数量 */
static int	maxSems;			/* allocated size of mySemaSet array */

static void fc_ReleaseSemaphores(int fc_code, Datum fc_arg);


/*
 * 报告信号量所需的共享内存量
 */
Size PGSemaphoreShmemSize(int fc_maxSemas)
{
	/* No shared memory needed on Windows */
	return 0;
}

/*
 * PGReserveSemaphores --- initialize semaphore support
 *
 * In the Win32 implementation, we acquire semaphores on-demand; the
 * maxSemas parameter is just used to size the array that keeps track of
 * acquired semas for subsequent releasing.  We use anonymous semaphores
 * so the semaphores are automatically freed when the last referencing
 * process exits.
 */
void PGReserveSemaphores(int fc_maxSemas)
{
	mySemSet = (HANDLE *) malloc(maxSemas * sizeof(HANDLE));
	if (mySemSet == NULL)
		elog(PANIC, "out of memory");
	numSems = 0;
	maxSems = fc_maxSemas;

	on_shmem_exit(fc_ReleaseSemaphores, 0);
}

/*
 * 在关闭或共享内存重新初始化时释放信号量
 *
 * （作为 on_shmem_exit 回调被调用，因此参数列表有点奇怪）
 */
static void fc_ReleaseSemaphores(int fc_code, Datum fc_arg)
{
	int			fc_i;

	for (fc_i = 0; fc_i < numSems; fc_i++)
		CloseHandle(mySemSet[fc_i]);
	free(mySemSet);
}

/*
 * PGSemaphoreCreate
 *
 * 分配一个初值为1的PGSemaphore结构
 */
PGSemaphore PGSemaphoreCreate(void)
{
	HANDLE		cur_handle;
	SECURITY_ATTRIBUTES sec_attrs;

	/* 在后端无法执行此操作，因为静态状态属于主进程 */
	Assert(!IsUnderPostmaster);

	if (numSems >= maxSems)
		elog(PANIC, "too many semaphores created");

	ZeroMemory(&sec_attrs, sizeof(sec_attrs));
	sec_attrs.nLength = sizeof(sec_attrs);
	sec_attrs.lpSecurityDescriptor = NULL;
	sec_attrs.bInheritHandle = TRUE;

	/* We don't need a named semaphore */
	cur_handle = CreateSemaphore(&sec_attrs, 1, 32767, NULL);
	if (cur_handle)
	{
		/* Successfully done */
		mySemSet[numSems++] = cur_handle;
	}
	else
		ereport(PANIC,
				(errmsg("could not create semaphore: error code %lu",
						GetLastError())));

	return (PGSemaphore) cur_handle;
}

/*
 * PGSemaphoreReset
 *
 * 将先前初始化的PGSemaphore重置为计数0
 */
void PGSemaphoreReset(PGSemaphore fc_sema)
{
	/*
	 * There's no direct API for this in Win32, so we have to ratchet the
	 * semaphore down to 0 with repeated trylock's.
	 */
	while (PGSemaphoreTryLock(fc_sema))
		 /* 循环 */ ;
}

/*
 * PGSemaphoreLock
 *
 * Lock a semaphore (decrement count), blocking if count would be < 0.
 */
void PGSemaphoreLock(PGSemaphore fc_sema)
{
	HANDLE		wh[2];
	bool		fc_done = false;

	/*
	 * Note: pgwin32_signal_event should be first to ensure that it will be
	 * reported when multiple events are set.  We want to guarantee that
	 * pending signals are serviced.
	 */
	wh[0] = pgwin32_signal_event;
	wh[1] = fc_sema;

	/*
	 * As in other implementations of PGSemaphoreLock, we need to check for
	 * cancel/die interrupts each time through the loop.  But here, there is
	 * no hidden magic about whether the syscall will internally service a
	 * signal --- we do that ourselves.
	 */
	while (!fc_done)
	{
		DWORD		rc;

		CHECK_FOR_INTERRUPTS();

		rc = WaitForMultipleObjectsEx(2, wh, FALSE, INFINITE, TRUE);
		switch (rc)
		{
			case WAIT_OBJECT_0:
				/* Signal event is set - we have a signal to deliver */
				pgwin32_dispatch_queued_signals();
				break;
			case WAIT_OBJECT_0 + 1:
				/* We got it! */
				fc_done = true;
				break;
			case WAIT_IO_COMPLETION:

				/*
				 * The system interrupted the wait to execute an I/O
				 * completion routine or asynchronous procedure call in this
				 * thread.  PostgreSQL does not provoke either of these, but
				 * atypical loaded DLLs or even other processes might do so.
				 * Now, resume waiting.
				 */
				break;
			case WAIT_FAILED:
				ereport(FATAL,
						(errmsg("could not lock semaphore: error code %lu",
								GetLastError())));
				break;
			default:
				elog(FATAL, "unexpected return code from WaitForMultipleObjectsEx(): %lu", rc);
				break;
		}
	}
}

/*
 * PGSemaphoreUnlock
 *
 * 解锁一个信号量（增加计数）
 */
void PGSemaphoreUnlock(PGSemaphore fc_sema)
{
	if (!ReleaseSemaphore(fc_sema, 1, NULL))
		ereport(FATAL,
				(errmsg("could not unlock semaphore: error code %lu",
						GetLastError())));
}

/*
 * PGSemaphoreTryLock
 *
 * 仅在能够而不阻塞的情况下锁定信号量
 */
bool PGSemaphoreTryLock(PGSemaphore fc_sema)
{
	DWORD		ret;

	ret = WaitForSingleObject(sema, 0);

	if (ret == WAIT_OBJECT_0)
	{
		/* We got it! */
		return true;
	}
	else if (ret == WAIT_TIMEOUT)
	{
		/* Can't get it */
		errno = EAGAIN;
		return false;
	}

	/* Otherwise we are in trouble */
	ereport(FATAL,
			(errmsg("could not try-lock semaphore: error code %lu",
					GetLastError())));

	/* 保持编译器安静 */
	return false;
}
